package org.dromara.starter.jpacomment.service;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.metamodel.EntityType;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.starter.jpacomment.annotation.SailColumn;
import org.dromara.starter.jpacomment.annotation.SailIndex;
import org.dromara.starter.jpacomment.annotation.SailTable;
import org.dromara.starter.jpacomment.dto.ColumnCommentDTO;
import org.dromara.starter.jpacomment.dto.SailIndexDTO;
import org.dromara.starter.jpacomment.dto.TableCommentDTO;
import org.dromara.starter.jpacomment.enums.IndexType;
import org.dromara.starter.jpacomment.properties.JpacommentProperties;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMappingsList;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;

/**
 * JPA 字段注释处理类
 * jpacommentService.alterAllTableAndColumn();
 * @author <a href="mailto:guzhongtao@middol.com">guzhongtao</a>
 */

@Service
public class JpacommentService {

    public static Logger log = LoggerFactory.getLogger(JpacommentService.class);

    // TODO 删除会报错
    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private AlterCommentService alterCommentService;

    @Autowired
    private JpacommentProperties jpacommentProperties;

    private Map<String, TableCommentDTO> dtoMap;

    @PostConstruct
    public void init() {
        if (entityManager == null) {
            log.error("EntityManager is null");
            throw new IllegalStateException("EntityManager is not injected");
        }
        dtoMap = findAllTableAndColumn();
        // 启动时执行
        if(jpacommentProperties.isEnable()){
            alterAllTableAndColumn();
        }
        System.out.println("JpacommentService 初始化成功...");
    }

    /**
     * 设置当前 schema 用于中途修改schema
     *
     * @param schema 模式 mysql来说就是database
     */
    public void setCurrentSchema(String schema) {
        alterCommentService.setSchema(schema);
    }

    /**
     * 用于中途修改 数据源的可能
     *
     * @param jdbcTemplate jdbcTemplate
     */
    public void setCurrentJdbcTemplate(JdbcTemplate jdbcTemplate) {
        alterCommentService.setJdbcTemplate(jdbcTemplate);
    }

    /**
     * 更新整个数据库的表注释和字段注释，非空情况下才更新
     */
    public void alterAllTableAndColumn() {
        Map<String, TableCommentDTO> dtoMap = findAllTableAndColumn();
        dtoMap.forEach((k, v) -> {
            try {
                alterSingleTableAndColumn(k);
            } catch (Exception e) {
                log.error("tableName '{}' ALTER comment exception ", k, e);
            }
        });
    }

    private String getIndexName(IndexType indexType, List<String> columnNames){
        String indexPrefix = jpacommentProperties.getIndexPrefix();
        StringJoiner joiner = new StringJoiner("_");
        // idx
        joiner.add(indexPrefix);
        // idx_name
        columnNames.forEach(joiner::add);
        // idx_name_fulltext
        if(StringUtils.isNotBlank(indexType.getValue())){
            joiner.add(indexType.getValue());
        }
        return joiner.toString();
    }
    /**
     * 更新单个数据库的表注释和字段注释，非空情况下才更新
     *
     * @param tableName 数据库表名称
     */
    public void alterSingleTableAndColumn(String tableName) {
        TableCommentDTO commentDTO = dtoMap.get(tableName);
        if (commentDTO != null) {
            //TODO 对比数据库如果有变动就更新，无变动不更新（避免启动时间长）
            if (StrUtil.isNotBlank(commentDTO.getComment())) {
                if (log.isDebugEnabled()) {
                    log.debug("==================== 修改表：表 {} ====================", tableName);
                    log.debug("修改表注释：注释 '{}'",  commentDTO.getComment());
                }
                alterCommentService.alterTableComment(commentDTO.getName(), commentDTO.getComment());
                // SAIL 临时解决 关联表 删除索引问题
                try{
                    log.debug("删除非PRIMARY索引");
                    alterCommentService.alterDropColumnIndex(commentDTO.getName());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    for(SailIndexDTO item : commentDTO.getIndexDTOList()){
                        // 设置默认索引名称
                        String indexName = StringUtils.isBlank(item.getIndexName())? getIndexName(item.getIndexType(), item.getColumns()): item.getIndexName() ;
                        log.debug("添加表索引：字段 '{}'; 类型 {}; 名称 {}", item.getColumns(),item.getIndexType(),indexName);
                        alterCommentService.alterAddColumnIndex(commentDTO.getName(), item.getIndexType(),indexName, item.getColumns());
                    }
                }
            }
            commentDTO.getColumnDTOList().forEach(
                    item -> {
                        // SAIL 没有该if判断，会遍历所有字段
                        if(StrUtil.isNotBlank(item.getComment())){
                            log.debug("修改表字段：表 {} 字段 {}; 注释 '{}'; 类型 '{}'; 是否允许NULL '{}'; 默认值 '{}'",tableName, item.getName(),item.getComment(),item.getType(),item.getNullable(),item.getDefaultValue());
                            alterCommentService.alterColumn(commentDTO.getName(), item.getName(),item.getType(), item.getNullable() ,item.getDefaultValue(), item.getComment());
                        }
                    });

        } else {
            log.warn("tableName '{}' not find in JPA ", tableName);
        }
    }


    @SuppressWarnings("rawtypes")
    public Map<String, TableCommentDTO> findAllTableAndColumn() {
        Map<String, TableCommentDTO> tableCommentMap = new HashMap<>(256);

        EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory();
        SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap(SessionFactoryImplementor.class);
        MetamodelImplementor metamodel = sessionFactory.getMetamodel();

        for (EntityType<?> entityType : metamodel.getEntities()) {
            EntityPersister entityPersister = metamodel.entityPersister(entityType.getJavaType());
            SingleTableEntityPersister persister = (SingleTableEntityPersister) entityPersister;
            Class targetClass = entityPersister.getMappedClass();
            TableCommentDTO table = new TableCommentDTO();
            // 表注释
            getTableInfo(persister, table, targetClass);
            //除主键外的属性注释
            getColumnInfo(persister, table, targetClass);
            // 主键字段注释
            getKeyColumnInfo(persister, table, targetClass);

            tableCommentMap.put(table.getName(), table);
        }

        return tableCommentMap;
    }

    @SuppressWarnings("rawtypes")
    private void getTableInfo(SingleTableEntityPersister persister, TableCommentDTO table, Class targetClass) {
        table.setColumnDTOList(new ArrayList<>(32));
        table.setName(persister.getTableName());

        SailTable sailTable = AnnotationUtil.getAnnotation(targetClass, SailTable.class);
        if (sailTable != null) {
            table.setComment(sailTable.comment());

            List<SailIndexDTO> indexDTOList = new ArrayList<>();
            for (SailIndex index : sailTable.indexs()) {
                SailIndexDTO indexDTO = new SailIndexDTO();
                indexDTO.setIndexType(index.type()); // 将枚举转换为字符串
                indexDTO.setColumns(List.of(index.columns())); // 转换数组为列表
                indexDTO.setIndexName(index.name());
                indexDTOList.add(indexDTO);
            }
            table.setIndexDTOList(indexDTOList);
        } else {
            table.setComment("");
        }
    }

    /**
     * 递归获取所有父类的类对象 包括自己
     * 最后的子类在第一个
     *
     * @param targetClass targetClass
     * @param list        list
     */
    @SuppressWarnings("rawtypes")
    private void getAllClass(Class targetClass, List<Class> list) {
        list.add(targetClass);

        if (!Object.class.equals(targetClass.getSuperclass())) {
            getAllClass(targetClass.getSuperclass(), list);
        }
    }

    @SuppressWarnings("rawtypes")
    private void getColumnInfo(SingleTableEntityPersister persister, TableCommentDTO table, Class targetClass) {
        // 情况比较复杂，必须还要判断是否有父类，存在父类则还要取父类的字段信息，优先取得子类字段为依据
        List<Class> classList = new ArrayList<>(2);
        getAllClass(targetClass, classList);

        Set<String> alreadyDealField = new HashSet<>(32);
        Set<String> allColumnField = new HashSet<>(32);

        AttributeMappingsList attributes = persister.getAttributeMappings();
        attributes.forEach(attr -> allColumnField.add(attr.getAttributeName()));

        classList.forEach(classItem -> Arrays.stream(ClassUtil.getDeclaredFields(classItem)).forEach(field -> {
            if (allColumnField.contains(field.getName())) {
                // 判断是否已经处理过
                if (!alreadyDealField.contains(field.getName())) {
                    //对应数据库表中的字段名
                    String[] columnName = persister.getPropertyColumnNames(field.getName());
                    getColumnComment(table, classItem, field.getName(), columnName);
                    alreadyDealField.add(field.getName());
                }
            }
        }));
    }

    @SuppressWarnings("rawtypes")
    private void getKeyColumnInfo(SingleTableEntityPersister persister, TableCommentDTO table, Class targetClass) {
        String idName = persister.getIdentifierPropertyName();
        String[] idColumns = persister.getIdentifierColumnNames();
        getColumnComment(table, targetClass, idName, idColumns);
    }

    @SuppressWarnings("rawtypes")
    private void getColumnComment(TableCommentDTO table, Class targetClass, String propertyName, String[] columnName) {
        SailColumn idSailColumn = AnnotationUtil.getAnnotation(
                ClassUtil.getDeclaredField(targetClass, propertyName), SailColumn.class);
        Arrays.stream(columnName).forEach(item -> {
            ColumnCommentDTO column = new ColumnCommentDTO();
            column.setName(item);
            // SAIL不校验报错
            if (!ObjectUtils.isEmpty(idSailColumn)) {
                column.setComment(idSailColumn.comment());
                column.setType(idSailColumn.type());
                column.setDefaultValue(idSailColumn.defaultValue());
                column.setNullable(idSailColumn.nullable());
            } else {
                column.setComment("");
            }
            table.getColumnDTOList().add(column);
        });
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void setAlterCommentService(AlterCommentService alterCommentService) {
        this.alterCommentService = alterCommentService;
    }
}
